"""
智能文字区域检测器

基于计算机视觉技术，检测手指指向的文字区域和完整段落/题目。
核心思路：
1. 利用文字在二值化后为黑色的特点
2. 考虑孩子指题目时不会遮挡，通常在四周
3. 利用文字连续性，通过形态学操作连接分散字符
4. 扩展检测区域以包含完整段落或题目
"""

import cv2
import numpy as np
import math
from typing import Optional, Tuple, List
from dataclasses import dataclass


@dataclass
class TextRegionResult:
    """文字区域检测结果"""
    # 检测到的文字区域边界框 (x, y, width, height)
    text_box: Optional[Tuple[int, int, int, int]]
    # 置信度
    confidence: float
    # 区域面积
    area: int
    # 是否检测成功
    success: bool
    # 错误信息
    error_message: Optional[str] = None
    # 调试信息：二值化图像
    binary_image: Optional[np.ndarray] = None
    # 调试信息：连接后的图像
    connected_image: Optional[np.ndarray] = None


class TextRegionDetector:
    """
    智能文字区域检测器
    
    功能：
    - 基于手指指向检测文字区域
    - 自动扩展到完整段落/题目
    - 适应不同光照和字体大小
    - 提供调试信息
    """
    
    def __init__(
        self,
        adaptive_block_size: int = 15,
        adaptive_c: int = 5,
        min_text_area: int = 100,
        min_char_size: int = 10,
        horizontal_connect_kernel: int = 15,
        vertical_connect_kernel: int = 5,
        expand_margin: int = 20,
        debug_mode: bool = False
    ):
        """
        初始化文字区域检测器
        
        参数:
            adaptive_block_size: 自适应二值化块大小
            adaptive_c: 自适应二值化常数
            min_text_area: 最小文字区域面积
            min_char_size: 最小字符尺寸
            horizontal_connect_kernel: 水平连接核大小
            vertical_connect_kernel: 垂直连接核大小
            expand_margin: 区域扩展边距
            debug_mode: 是否启用调试模式
        """
        self.adaptive_block_size = adaptive_block_size
        self.adaptive_c = adaptive_c
        self.min_text_area = min_text_area
        self.min_char_size = min_char_size
        self.horizontal_connect_kernel = horizontal_connect_kernel
        self.vertical_connect_kernel = vertical_connect_kernel
        self.expand_margin = expand_margin
        self.debug_mode = debug_mode
    
    def detect_pointed_text_region(
        self, 
        image: np.ndarray, 
        finger_tip: Tuple[int, int], 
        finger_direction: Tuple[float, float],
        search_length: int = 200
    ) -> TextRegionResult:
        """
        检测手指指向的文字区域
        
        参数:
            image: 输入图像
            finger_tip: 手指指尖坐标 (x, y)
            finger_direction: 手指方向向量 (dx, dy)
            search_length: 搜索长度
            
        返回:
            TextRegionResult: 检测结果
        """
        if image is None or image.size == 0:
            return TextRegionResult(
                text_box=None, confidence=0.0, area=0, success=False,
                error_message="输入图像为空"
            )
        
        try:
            # 1. 图像预处理和二值化
            binary_image = self._binarize_image(image)
            
            # 2. 形态学操作连接文字
            connected_image = self._connect_text_characters(binary_image)
            
            # 3. 查找文字区域
            text_box = self._find_text_region_by_pointing(
                connected_image, finger_tip, finger_direction, search_length
            )
            
            if text_box is None:
                return TextRegionResult(
                    text_box=None, confidence=0.0, area=0, success=False,
                    error_message="未找到指向的文字区域",
                    binary_image=binary_image if self.debug_mode else None,
                    connected_image=connected_image if self.debug_mode else None
                )
            
            # 4. 扩展区域以包含完整段落
            expanded_box = self._expand_to_complete_paragraph(
                connected_image, text_box, image.shape[:2]
            )
            
            # 5. 计算置信度和面积
            area = expanded_box[2] * expanded_box[3]
            confidence = min(1.0, area / (image.shape[0] * image.shape[1] * 0.1))
            
            return TextRegionResult(
                text_box=expanded_box,
                confidence=confidence,
                area=area,
                success=True,
                binary_image=binary_image if self.debug_mode else None,
                connected_image=connected_image if self.debug_mode else None
            )
            
        except Exception as e:
            return TextRegionResult(
                text_box=None, confidence=0.0, area=0, success=False,
                error_message=f"检测过程出错: {str(e)}"
            )
    
    def _binarize_image(self, image: np.ndarray) -> np.ndarray:
        """图像二值化处理"""
        # 转为灰度图
        if len(image.shape) == 3:
            gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
        else:
            gray = image.copy()
        
        # 高斯模糊减少噪声
        blurred = cv2.GaussianBlur(gray, (3, 3), 0)
        
        # 自适应二值化，适应不同光照条件
        binary = cv2.adaptiveThreshold(
            blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
            cv2.THRESH_BINARY_INV, self.adaptive_block_size, self.adaptive_c
        )
        
        return binary
    
    def _connect_text_characters(self, binary_image: np.ndarray) -> np.ndarray:
        """通过形态学操作连接文字字符"""
        # 水平连接核 - 连接同一行的字符
        h_kernel = cv2.getStructuringElement(
            cv2.MORPH_RECT, (self.horizontal_connect_kernel, 1)
        )
        h_connected = cv2.morphologyEx(binary_image, cv2.MORPH_CLOSE, h_kernel)
        
        # 垂直连接核 - 连接相邻行
        v_kernel = cv2.getStructuringElement(
            cv2.MORPH_RECT, (1, self.vertical_connect_kernel)
        )
        connected = cv2.morphologyEx(h_connected, cv2.MORPH_CLOSE, v_kernel)
        
        return connected
    
    def _find_text_region_by_pointing(
        self, 
        binary_image: np.ndarray, 
        finger_tip: Tuple[int, int], 
        finger_direction: Tuple[float, float],
        search_length: int
    ) -> Optional[Tuple[int, int, int, int]]:
        """沿手指方向查找文字区域"""
        tip_x, tip_y = finger_tip
        dx, dy = finger_direction
        
        # 查找连通区域
        contours, _ = cv2.findContours(
            binary_image, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
        )
        
        if not contours:
            return None
        
        # 沿手指方向搜索
        best_region = None
        min_distance = float('inf')
        
        for i in range(0, search_length, 5):
            search_x = int(tip_x + dx * i)
            search_y = int(tip_y + dy * i)
            
            # 检查搜索点是否在图像范围内
            if not (0 <= search_x < binary_image.shape[1] and 
                    0 <= search_y < binary_image.shape[0]):
                continue
            
            # 查找包含此点的文字区域
            for contour in contours:
                if cv2.pointPolygonTest(contour, (search_x, search_y), False) >= 0:
                    x, y, w, h = cv2.boundingRect(contour)
                    area = w * h
                    
                    # 过滤条件
                    if (area < self.min_text_area or 
                        w < self.min_char_size or 
                        h < self.min_char_size):
                        continue
                    
                    # 计算距离
                    distance = math.sqrt((search_x - tip_x)**2 + (search_y - tip_y)**2)
                    
                    if distance < min_distance:
                        min_distance = distance
                        best_region = (x, y, w, h)
                        break
            
            if best_region:
                break
        
        # 如果沿方向没找到，查找最近的文字区域
        if not best_region:
            for contour in contours:
                x, y, w, h = cv2.boundingRect(contour)
                area = w * h
                
                if (area < self.min_text_area or 
                    w < self.min_char_size or 
                    h < self.min_char_size):
                    continue
                
                # 计算区域中心到手指的距离
                center_x = x + w // 2
                center_y = y + h // 2
                distance = math.sqrt((center_x - tip_x)**2 + (center_y - tip_y)**2)
                
                if distance < min_distance:
                    min_distance = distance
                    best_region = (x, y, w, h)
        
        return best_region
    
    def _expand_to_complete_paragraph(
        self, 
        binary_image: np.ndarray, 
        region: Tuple[int, int, int, int],
        image_shape: Tuple[int, int]
    ) -> Tuple[int, int, int, int]:
        """扩展区域以包含完整段落"""
        x, y, w, h = region
        img_h, img_w = image_shape
        
        # 扩展边界
        new_x = max(0, x - self.expand_margin)
        new_y = max(0, y - self.expand_margin)
        new_x2 = min(img_w, x + w + self.expand_margin)
        new_y2 = min(img_h, y + h + self.expand_margin)
        
        # 在扩展区域内查找所有文字
        expanded_region = binary_image[new_y:new_y2, new_x:new_x2]
        contours, _ = cv2.findContours(
            expanded_region, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
        )
        
        if contours:
            # 合并所有相近的文字区域
            all_points = []
            for contour in contours:
                area = cv2.contourArea(contour)
                if area > 50:  # 过滤小噪点
                    # 将轮廓点转换回原图坐标
                    for point in contour:
                        all_points.append([point[0][0] + new_x, point[0][1] + new_y])
            
            if all_points:
                all_points = np.array(all_points)
                final_x = int(np.min(all_points[:, 0]))
                final_y = int(np.min(all_points[:, 1]))
                final_x2 = int(np.max(all_points[:, 0]))
                final_y2 = int(np.max(all_points[:, 1]))
                
                return (final_x, final_y, final_x2 - final_x, final_y2 - final_y)
        
        # 如果扩展失败，返回原始区域
        return region
